﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using JEng_Mark_1.GameComponents;

namespace JEng_Mark_1.Managers
{
    [Serializable()]
    public class JFileVerifyManager : ISerializable
    {
        private Dictionary<string, byte[]> _fileHashes;

        /// <summary>
        /// Get the file hashes object
        /// </summary>
        public Dictionary<string, byte[]> FileHashes { get { return _fileHashes; } }

        private static readonly string _encPass = "TEMPPASS";

        /// <summary>
        /// Construct an empty file verifyer
        /// </summary>
        public JFileVerifyManager()
        {
#if XBOX360
            //no need to load or init on xbox
            return;
#endif
            if (File.Exists("Content/security.sec"))
            {
                _fileHashes = Load("Content/security.sec").FileHashes;
            }
            else
            {
                _fileHashes = new Dictionary<string, byte[]>();
            }
        }

        ~JFileVerifyManager()
        {
#if XBOX360
            //no need to save
            return;
#endif
            //save the instance
            Write(this, "Content/security.sec");
        }

        /// <summary>
        /// Deserialization constructor
        /// </summary>
        /// <param name="info"></param>
        /// <param name="stct"></param>
        public JFileVerifyManager(SerializationInfo info, StreamingContext stct)
        {
            _fileHashes = (Dictionary<string, byte[]>)info.GetValue("_fileHashes", typeof(Dictionary<string, byte[]>));
        }

        /// <summary>
        /// Store values for serialization
        /// </summary>
        /// <param name="info"></param>
        /// <param name="stct"></param>
        public void GetObjectData(SerializationInfo info, StreamingContext stct)
        {
            info.AddValue("_fileHashes", _fileHashes);
        }

        /// <summary>
        /// Check to see if a file have been tampered with outside of the engine
        /// </summary>
        /// <param name="filePath">Path of file to check for tampering</param>
        /// <returns>True if the file has not been tampered with, false if it has</returns>
        public bool IsFileValid(string filePath)
        {
#if XBOX36
            //It should be impossible to tamper with files on the xbox360
            return true;
#endif

            //check if file is in tracking list
            if(!_fileHashes.ContainsKey(filePath)) return false;

            //Get the file's SHA1 hash
            using (HashAlgorithm hashAlg = new SHA1Managed())
            {
                using (Stream file = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                {
                    byte[] hash = hashAlg.ComputeHash(file);
                    //check if file hashes match
                    for (int i = 0; i < 20; i++)
                    {
                        if (hash[i] != _fileHashes[filePath][i])
                        {
                            JLog.addError("File, " + filePath + " has been tampered with!");
                            return false;
                        }
                    }
                }
            }
            JLog.addMessage("File, " + filePath + " verified as untampered!");
            return true;
        }

        /// <summary>
        /// Tell the file verifyer that we have updated a file and that it should update tamper information
        /// </summary>
        /// <param name="filePath">File that has been updated</param>
        public void UpdateFile(string filePath)
        {
#if XBOX360
            //no need to update, xbox doesn't allow for tampering
            return;
#endif
            //Get the file's SHA1 hash
            using (HashAlgorithm hashAlg = new SHA1Managed())
            {
                using (Stream file = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                {
                    byte[] hash = hashAlg.ComputeHash(file);

                    //check if file is in tracking list
                    if (!_fileHashes.ContainsKey(filePath))
                    {
                        _fileHashes.Add(filePath, hash);
                        return;
                    }
                    else
                    {
                        _fileHashes[filePath] = hash;
                    }
                }
            }
        }

        /// <summary>
        /// Add a file to the list of tracked files, if the file has already been added then disregard
        /// </summary>
        /// <param name="filePath">File to add to tracking list</param>
        public void AddFile(string filePath)
        {
#if XBOX360
            //there is no need to add the file, because xbox360 cannot allow tampering
            return;
#endif
            if (!_fileHashes.ContainsKey(filePath))
            {
                //Get the file's SHA1 hash
                using (HashAlgorithm hashAlg = new SHA1Managed())
                {
                    using (Stream file = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                    {
                        byte[] hash = hashAlg.ComputeHash(file);

                        _fileHashes.Add(filePath, hash);
                        JLog.addMessage("File, " + filePath + " has been added to tamper tracking");
                    }
                }
            }
        }

        /// <summary>
        /// Write a JFileVerifyManager to file as an encrypted binary stream
        /// </summary>
        /// <param name="fvm">Manager to save</param>
        public static void Write(JFileVerifyManager fvm,string outputPath)
        {
#if XBOX360
            //We don't need to save the file on an xbox, because tampering cannot occur
            return;
#endif

            try
            {
                BinaryFormatter bformatter = new BinaryFormatter();
                
                UnicodeEncoding UE = new UnicodeEncoding();
                byte[] key = UE.GetBytes(_encPass);

                string cryptFile = outputPath;
                FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create);

                RijndaelManaged RMCrypto = new RijndaelManaged();

                CryptoStream cs = new CryptoStream(fsCrypt,
                    RMCrypto.CreateEncryptor(key, key),
                    CryptoStreamMode.Write);

                bformatter.Serialize(cs, fvm);

                cs.Close();
                fsCrypt.Close();
            }
            catch
            {
                JLog.addError("Could not encrypt file!");
            }
        }

        /// <summary>
        /// Load a file verify manager from disk and unencrypt it
        /// </summary>
        /// <param name="path">File location to load</param>
        /// <returns>The unencrypted file verify manager</returns>
        public static JFileVerifyManager Load(string path)
        {
            BinaryFormatter bnformat = new BinaryFormatter();
            UnicodeEncoding UE = new UnicodeEncoding();
            byte[] key = UE.GetBytes(_encPass);

            FileStream fsCrypt = new FileStream(path, FileMode.Open);

            RijndaelManaged RMCrypto = new RijndaelManaged();

            CryptoStream cs = new CryptoStream(fsCrypt,
                RMCrypto.CreateDecryptor(key, key),
                CryptoStreamMode.Read);

            JFileVerifyManager fvm = (JFileVerifyManager)bnformat.Deserialize(cs);
           
            cs.Close();
            fsCrypt.Close();

            return fvm;
        }
    }
}
